/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.nus.dataaccess.dao.util;

import org.hibernate.cfg.*;
import org.hibernate.Interceptor;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import org.apache.commons.logging.*;
import javax.naming.*;


@SuppressWarnings("all")
public class HibernateUtil {

    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final String INTERCEPTOR_CLASS = "hibernate.util.interceptor_class";
    private static Configuration configuration;
    private static SessionFactory sessionFactory;
    private static ThreadLocal threadSession = new ThreadLocal();
    private static ThreadLocal threadTransaction = new ThreadLocal();
    private static boolean useThreadLocal = true;
    
    

    static {
        // Create the initial SessionFactory from the default configuration files
        try {

             configuration = new AnnotationConfiguration();

            // Read not only hibernate.properties, but also hibernate.cfg.xml
            configuration.configure();

            // Assign a global, user-defined interceptor with no-arg constructor
            String interceptorName = configuration.getProperty(INTERCEPTOR_CLASS);
            if (interceptorName != null) {
                Class interceptorClass =
                        HibernateUtil.class.getClassLoader().loadClass(interceptorName);
                Interceptor interceptor = (Interceptor) interceptorClass.newInstance();
                configuration.setInterceptor(interceptor);
            }

            // Disable ThreadLocal Session/Transaction handling if CMT is used
//            if (org.hibernate.transaction.CMTTransactionFactory.class.getName()
//                    .equals(configuration.getProperty(Environment.TRANSACTION_STRATEGY))) {
//                useThreadLocal = false;
//            }

            if (configuration.getProperty(Environment.SESSION_FACTORY_NAME) != null) {
                // Let Hibernate bind it to JNDI
                configuration.buildSessionFactory();
            } else {
                // or use static variable handling
                sessionFactory = configuration.buildSessionFactory();
            }

        } catch (Throwable ex) {
            // We have to catch Throwable, otherwise we will miss
            // NoClassDefFoundError and other subclasses of Error
            ex.printStackTrace();
            log.error("Building SessionFactory failed.", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static Configuration getConfiguration() {
        return configuration;
    }

    /**
     * Returns the global SessionFactory.
     *
     * @return SessionFactory
     */
    public static SessionFactory getSessionFactory() {
        SessionFactory sf = null;
        String sfName = configuration.getProperty(Environment.SESSION_FACTORY_NAME);
        if (sfName != null) {
            log.debug("Looking up SessionFactory in JNDI.");
            try {
                sf = (SessionFactory) new InitialContext().lookup(sfName);
            } catch (NamingException ex) {
                // throw new RuntimeException(ex);
                log.error(ex);
            }
        } else {
            sf = sessionFactory;
        }
        if (sf == null) {
            throw new IllegalStateException("SessionFactory not available.");
        }
        return sf;
    }

    /**
     * Closes the current SessionFactory and releases all resources. <p> The
     * only other method that can be called on HibernateUtil after this one is
     * rebuildSessionFactory(Configuration).
     */
    public static void shutdown() {
        log.debug("Shutting down Hibernate.");
        // Close caches and connection pools
        getSessionFactory().close();

        // Clear static variables
        configuration = null;
        sessionFactory = null;

        // Clear ThreadLocal variables
        threadSession.set(null);
        threadTransaction.set(null);
    }

    /**
     * Rebuild the SessionFactory with the static Configuration. <p> This method
     * also closes the old SessionFactory before, if still open. Note that this
     * method should only be used with static SessionFactory management, not
     * with JNDI or any other external registry.
     */
    public static void rebuildSessionFactory() {
        log.debug("Using current Configuration for rebuild.");
        rebuildSessionFactory(configuration);
    }

    /**
     * Rebuild the SessionFactory with the given Hibernate Configuration. <p>
     * HibernateUtil does not configure() the given Configuration object, it
     * directly calls buildSessionFactory(). This method also closes the old
     * SessionFactory before, if still open.
     *
     * @param cfg
     */
    public static void rebuildSessionFactory(Configuration cfg) {
        log.debug("Rebuilding the SessionFactory from given Configuration.");
        synchronized (sessionFactory) {
            if (sessionFactory != null && !sessionFactory.isClosed()) {
                sessionFactory.close();
            }
            if (cfg.getProperty(Environment.SESSION_FACTORY_NAME) != null) {
                cfg.buildSessionFactory();
            } else {
                sessionFactory = cfg.buildSessionFactory();
            }
            configuration = cfg;
        }
    }

    /**
     * Retrieves the current Session local to the thread.
     * <p/>
     * If no Session is open, opens a new Session for the running thread. If CMT
     * is used, returns the Session bound to the current JTA container
     * transaction. Most other operations on this class will then be no-ops or
     * not supported, the container handles Session and Transaction boundaries,
     * ThreadLocals are not used.
     *
     * @return Session
     */
    public static Session getCurrentSession() {
        if (useThreadLocal) {
            Session s = (Session) threadSession.get();
            if (s == null) {
//				AuditLogInterceptor interceptor = new AuditLogInterceptor();
                log.debug("Opening new Session for this thread.");
                //			s = getSessionFactory().openSession(interceptor);
                s = getSessionFactory().openSession();
//				interceptor.setSession(s);

                threadSession.set(s);
            }
            return s;
        } else {
            return getSessionFactory().getCurrentSession();
        }
    }

    /**
     * Closes the Session local to the thread. <p> Is a no-op (with warning) if
     * called in a CMT environment. Should be used in non-managed environments
     * with resource local transactions, or with EJBs and bean-managed
     * transactions.
     */
    public static void closeSession() {
        if (useThreadLocal) {
            Session s = (Session) threadSession.get();
            threadSession.set(null);
            if (s != null && s.isOpen()) {
                s.clear();
                log.debug("Closing Session of this thread.");
                s.close();
            }
        } else {
            log.warn("Using CMT/JTA, intercepted superfluous close call.");
        }
    }

    /**
     * Start a new database transaction. <p> Is a no-op (with warning) if called
     * in a CMT environment. Should be used in non-managed environments with
     * resource local transactions, or with EJBs and bean-managed transactions.
     * In both cases, it will either start a new transaction or join the
     * existing ThreadLocal or JTA transaction.
     */
    public static void beginTransaction() {
        if (useThreadLocal) {
            Transaction tx = (Transaction) threadTransaction.get();
            if (tx == null) {
                log.debug("Starting new database transaction in this thread.");
                tx = getCurrentSession().beginTransaction();
                threadTransaction.set(tx);
            }
        } else {
            log.warn("Using CMT/JTA, intercepted superfluous tx begin call.");
        }
    }

    /**
     * Commit the database transaction. <p> Is a no-op (with warning) if called
     * in a CMT environment. Should be used in non-managed environments with
     * resource local transactions, or with EJBs and bean-managed transactions.
     * It will commit the ThreadLocal or BMT/JTA transaction.
     */
    public static void commitTransaction() {
        if (useThreadLocal) {
            Transaction tx = (Transaction) threadTransaction.get();
            try {
                if (tx != null && !tx.wasCommitted()
                        && !tx.wasRolledBack()) {
                    log.debug("Committing database transaction of this thread.");
                    tx.commit();
                }
                threadTransaction.set(null);
            } catch (RuntimeException ex) {
                log.error(ex);
                rollbackTransaction();
                throw ex;
            }
        } else {
            log.warn("Using CMT/JTA, intercepted superfluous tx commit call.");
        }
    }

    /**
     * Rollback the database transaction. <p> Is a no-op (with warning) if
     * called in a CMT environment. Should be used in non-managed environments
     * with resource local transactions, or with EJBs and bean-managed
     * transactions. It will rollback the resource local or BMT/JTA transaction.
     */
    public static void rollbackTransaction() {
        if (useThreadLocal) {
            Transaction tx = (Transaction) threadTransaction.get();
            try {
                threadTransaction.set(null);
                if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) {
                    log.debug("Tyring to rollback database transaction of this thread.");
                    tx.rollback();
                    log.debug("Database transaction rolled back.");
                }
            } catch (RuntimeException ex) {
                // throw new RuntimeException("Might swallow original cause, check ERROR log!", ex);
                log.error(ex);
            } finally {
                closeSession();
            }
        } else {
            log.warn("Using CMT/JTA, intercepted superfluous tx rollback call.");
        }
    }

    /**
     * Reconnects a Hibernate Session to the current Thread. <p> Unsupported in
     * a CMT environment.
     *
     * @param session The Hibernate Session to be reconnected.
     */
    public static void reconnect(Session session) {
        if (useThreadLocal) {
            log.debug("Reconnecting Session to this thread.");
            //TODO:: need to set the reconnection
//            session.reconnect();
            threadSession.set(session);
        } else {
            log.error("Using CMT/JTA, intercepted not supported reconnect call.");
        }
    }

    /**
     * Disconnect and return Session from current Thread.
     *
     * @return Session the disconnected Session
     */
    public static Session disconnectSession() {
        if (useThreadLocal) {
            Session session = getCurrentSession();
            threadSession.set(null);
            if (session.isConnected() && session.isOpen()) {
                log.debug("Disconnecting Session from this thread.");
                session.disconnect();
            }
            return session;
        } else {
            log.error("Using CMT/JTA, intercepted not supported disconnect call.");
            return null;
        }
    }

    /**
     * Register a Hibernate interceptor with the current SessionFactory. <p>
     * Every Session opened is opened with this interceptor after registration.
     * Has no effect if the current Session of the thread is already open,
     * effective on next close()/getCurrentSession(). <p> Attention: This method
     * effectively restarts Hibernate. If you need an interceptor active on
     * static startup of HibernateUtil, set the
     * <tt>hibernateutil.interceptor</tt> system property to its fully qualified
     * class name.
     */
    public static void registerInterceptorAndRebuild(Interceptor interceptor) {
        log.debug("Setting new global Hibernate interceptor and restarting.");
        configuration.setInterceptor(interceptor);
        rebuildSessionFactory();
    }

    public static Interceptor getInterceptor() {
        return configuration.getInterceptor();
    }

    public static boolean isUseThreadLocal() {
        return useThreadLocal;
    }

    public static void setUseThreadLocal(boolean useThreadLocal) {
        HibernateUtil.useThreadLocal = useThreadLocal;
    }
}
